Merge branch 'unstable' into support-subcategories

Conflicts:
	moulinette/actionsmap.py
This commit is contained in:
Alexandre Aubin 2017-07-29 12:38:18 -04:00
commit 0bbd47c090
9 changed files with 100 additions and 61 deletions

View file

@ -570,31 +570,10 @@ class ActionsMap(object):
"""
# Get extra parameters
if not self.use_cache:
validate_extra = True
else:
if self.use_cache:
validate_extra = False
# Add arguments to the parser
def _add_arguments(tid, parser, arguments):
# parser is argparse._ArgumentGroup for top_parser
# or ExtendedArgumentParser for other cases
# or maybe something else?
for argument_name, argument_options in arguments.items():
# will adapt arguments name for cli or api context
names = top_parser.format_arg_names(str(argument_name),
argument_options.pop('full', None))
if "type" in argument_options:
argument_options['type'] = eval(argument_options['type'])
if "extra" not in argument_options:
parser.add_argument(*names, **argument_options)
continue
extra = argument_options.pop('extra')
argument_dest = parser.add_argument(*names, **argument_options).dest
self.extraparser.add_argument(tid, argument_dest, extra, validate_extra)
else:
validate_extra = True
# Instantiate parser
#
@ -616,9 +595,7 @@ class ActionsMap(object):
top_parser.set_global_conf(_global['configuration'])
if top_parser.has_global_parser():
# GLOBAL_SECTION = '_global'
_add_arguments(GLOBAL_SECTION, top_parser.global_parser,
_global['arguments'])
top_parser.add_global_arguments(_global['arguments'])
# category_name is stuff like "user", "domain", "hooks"...
# category_values is the values of this category (like actions)
@ -635,7 +612,8 @@ class ActionsMap(object):
subcategories = {}
# Get category parser
category_parser = top_parser.add_category_parser(category_name, **category_values)
category_parser = top_parser.add_category_parser(category_name,
**category_values)
# action_name is like "list" of "domain list"
# action_options are the values
@ -643,20 +621,23 @@ class ActionsMap(object):
arguments = action_options.pop('arguments', {})
tid = (namespace, category_name, action_name)
try:
# Get action parser
action_parser = category_parser.add_action_parser(action_name, tid, **action_options)
except AttributeError:
# No parser for the action
# Get action parser
action_parser = category_parser.add_action_parser(action_name,
tid,
**action_options)
if action_parser is None: # No parser for the action
continue
# Store action identifier and add arguments
action_parser.set_defaults(_tid=tid)
_add_arguments(tid, action_parser, arguments)
action_parser.add_arguments(arguments,
extraparser=self.extraparser,
format_arg_names=top_parser.format_arg_names,
validate_extra=validate_extra)
if 'configuration' in action_options:
configuration = action_options.pop('configuration')
category_parser.set_conf(tid, configuration)
category_parser.set_conf(tid, action_options['configuration'])
# subcategory_name is like "cert" in "domain cert status"
# subcategory_values is the values of this subcategory (like actions)
@ -682,12 +663,12 @@ class ActionsMap(object):
# Store action identifier and add arguments
action_parser.set_defaults(_tid=tid)
_add_arguments(tid, action_parser, arguments)
action_parser.add_arguments(arguments,
extraparser=self.extraparser,
format_arg_names=top_parser.format_arg_names,
validate_extra=validate_extra)
if 'configuration' in action_options:
configuration = action_options.pop('configuration')
subcategory_parser.set_conf(tid, configuration)
category_parser.set_conf(tid, action_options['configuration'])
return top_parser

View file

@ -51,7 +51,7 @@ class BaseAuthenticator(object):
# Virtual methods
# Each authenticator classes must implement these methods.
def authenticate(password=None):
def authenticate(self, password=None):
"""Attempt to authenticate
Attempt to authenticate with given password. It should raise an

View file

@ -508,8 +508,8 @@ class _ExtendedSubParsersAction(argparse._SubParsersAction):
class ExtendedArgumentParser(argparse.ArgumentParser):
def __init__(self, *args, **kwargs):
super(ExtendedArgumentParser, self).__init__(
formatter_class=PositionalsFirstHelpFormatter, *args, **kwargs)
super(ExtendedArgumentParser, self).__init__(formatter_class=PositionalsFirstHelpFormatter,
*args, **kwargs)
# Register additional actions
self.register('action', 'callback', _CallbackAction)
@ -541,6 +541,24 @@ class ExtendedArgumentParser(argparse.ArgumentParser):
queue = list()
return queue
def add_arguments(self, arguments, extraparser, format_arg_names=None, validate_extra=True):
for argument_name, argument_options in arguments.items():
# will adapt arguments name for cli or api context
names = format_arg_names(str(argument_name),
argument_options.pop('full', None))
if "type" in argument_options:
argument_options['type'] = eval(argument_options['type'])
if "extra" in argument_options:
extra = argument_options.pop('extra')
argument_dest = self.add_argument(*names, **argument_options).dest
extraparser.add_argument(self.get_default("_tid"),
argument_dest, extra, validate_extra)
continue
self.add_argument(*names, **argument_options)
def _get_nargs_pattern(self, action):
if action.nargs == argparse.PARSER and not action.required:
return '([-AO]*)'

View file

@ -80,6 +80,24 @@ class _HTTPArgumentParser(object):
def get_default(self, dest):
return self._parser.get_default(dest)
def add_arguments(self, arguments, extraparser, format_arg_names=None, validate_extra=True):
for argument_name, argument_options in arguments.items():
# will adapt arguments name for cli or api context
names = format_arg_names(str(argument_name),
argument_options.pop('full', None))
if "type" in argument_options:
argument_options['type'] = eval(argument_options['type'])
if "extra" in argument_options:
extra = argument_options.pop('extra')
argument_dest = self.add_argument(*names, **argument_options).dest
extraparser.add_argument(self.get_default("_tid"),
argument_dest, extra, validate_extra)
continue
self.add_argument(*names, **argument_options)
def add_argument(self, *args, **kwargs):
action = self._parser.add_argument(*args, **kwargs)
@ -387,6 +405,15 @@ class _ActionsMapPlugin(object):
ret = self.actionsmap.process(arguments, timeout=30, route=_route)
except MoulinetteError as e:
raise error_to_response(e)
except Exception as e:
if isinstance(e, HTTPResponse):
raise e
import traceback
tb = traceback.format_exc()
logs = { "route": _route,
"arguments": arguments,
"traceback": tb }
return HTTPErrorResponse(json_encode(logs))
else:
return format_for_response(ret)
finally:
@ -581,7 +608,7 @@ class ActionsMapParser(BaseActionsMapParser):
if len(keys) == 0:
raise ValueError("no valid api route found")
else:
raise AttributeError("no api route for action '%s'" % name)
return None
# Create and append parser
parser = _HTTPArgumentParser()

View file

@ -308,6 +308,15 @@ class ActionsMapParser(BaseActionsMapParser):
deprecated=deprecated,
deprecated_alias=deprecated_alias)
def add_global_arguments(self, arguments):
for argument_name, argument_options in arguments.items():
# will adapt arguments name for cli or api context
names = self.format_arg_names(str(argument_name),
argument_options.pop('full', None))
self.global_parser.add_argument(*names, **argument_options)
def parse_args(self, args, **kwargs):
try:
ret = self._parser.parse_args(args)

View file

@ -9,9 +9,13 @@ sys.path.append("..")
old_init = moulinette.core.Moulinette18n.__init__
def monkey_path_i18n_init(self, package, default_locale="en"):
old_init(self, package, default_locale)
self.load_namespace("moulinette")
moulinette.core.Moulinette18n.__init__ = monkey_path_i18n_init
@ -21,16 +25,23 @@ moulinette.core.Moulinette18n.__init__ = monkey_path_i18n_init
old_translate = moulinette.core.Translator.translate
def new_translate(self, key, *args, **kwargs):
if key not in self._translations[self.default_locale].keys():
raise KeyError("Unable to retrieve key %s for default locale !" % key)
return old_translate(self, key, *args, **kwargs)
moulinette.core.Translator.translate = new_translate
def new_m18nn(self, key, *args, **kwargs):
return self._global.translate(key, *args, **kwargs)
moulinette.core.Moulinette18n.g = new_m18nn
@ -93,4 +104,3 @@ def pytest_cmdline_main(config):
# Initialize moulinette
moulinette.init(logging_config=logging, _from_source=False)

View file

@ -3,16 +3,14 @@
import os
import pwd
import pytest
import requests
import requests_mock
# Moulinette specific
from moulinette.core import MoulinetteError
from moulinette.utils.filesystem import (
read_file, read_json,
write_to_file, append_to_file, write_to_json,
rm,
chmod, chown)
from moulinette.utils.filesystem import (read_file, read_json,
write_to_file, append_to_file,
write_to_json,
rm,
chmod, chown)
# We define a dummy context with test folders and files
@ -61,7 +59,7 @@ def test_read_file():
def test_read_file_badfile():
with pytest.raises(MoulinetteError):
read_file(TMP_TEST_FILE+"nope")
read_file(TMP_TEST_FILE + "nope")
def test_read_file_badpermissions():
@ -295,5 +293,3 @@ def test_setpermissions_badgroup():
with pytest.raises(MoulinetteError):
set_permissions(TMP_TEST_FILE, NON_ROOT_USER, "foo", 0111)

View file

@ -1,7 +1,5 @@
# General python lib
import os
import pwd
import pytest
import requests
import requests_mock
@ -14,10 +12,12 @@ from moulinette.utils.network import download_text, download_json
TEST_URL = "https://some.test.url/yolo.txt"
def setup_function(function):
pass
def teardown_function(function):
pass
@ -88,5 +88,3 @@ def test_download_json_badjson():
with pytest.raises(MoulinetteError):
download_json(TEST_URL)

View file

@ -39,6 +39,7 @@ def switch_to_non_root_user():
# Test run shell commands #
###############################################################################
def test_run_shell_command_list():
commands = ["rm -f %s" % TMP_TEST_FILE]
@ -63,4 +64,3 @@ def test_run_shell_command_badpermissions():
switch_to_non_root_user()
with pytest.raises(CalledProcessError):
run_commands(commands)