diff --git a/plugins/module_utils/ansible_freeipa_module.py b/plugins/module_utils/ansible_freeipa_module.py index 45e956d08c6c5d89f5c2f08c93d4e94154cefb2c..45eabb88d322b4f97812facf1c9bb075c2eca80d 100644 --- a/plugins/module_utils/ansible_freeipa_module.py +++ b/plugins/module_utils/ansible_freeipa_module.py @@ -45,6 +45,7 @@ else: import gssapi from datetime import datetime from contextlib import contextmanager + import inspect # ansible-freeipa requires locale to be C, IPA requires utf-8. os.environ["LANGUAGE"] = "C" @@ -716,8 +717,21 @@ else: """ return api_check_ipa_version(oper, requested_version) - def execute_ipa_commands(self, commands, handle_result=None, - **handle_result_user_args): + # pylint: disable=unused-argument + @staticmethod + def member_error_handler(module, result, command, name, args, errors): + # Get all errors + for failed_item in result.get("failed", []): + failed = result["failed"][failed_item] + for member_type in failed: + for member, failure in failed[member_type]: + errors.append("%s: %s %s: %s" % ( + command, member_type, member, failure)) + + def execute_ipa_commands(self, commands, result_handler=None, + exception_handler=None, + fail_on_member_errors=False, + **handlers_user_args): """ Execute IPA API commands from command list. @@ -727,30 +741,56 @@ else: The list of commands in the form (name, command and args) For commands that do not require a 'name', None needs be used. - handle_result: function + result_handler: function The user function to handle results of the single commands - handle_result_user_args: dict (user args mapping) - The user args to pass to handle_result function + exception_handler: function + The user function to handle exceptions of the single commands + Returns True to continue to next command, else False + fail_on_member_errors: bool + Use default member error handler handler member_error_handler + handlers_user_args: dict (user args mapping) + The user args to pass to result_handler and exception_handler + functions Example (ipauser module): - def handle_result(module, result, command, name, args, exit_args): + def result_handler(module, result, command, name, args, exit_args, + one_name): if "random" in args and command in ["user_add", "user_mod"] \ and "randompassword" in result["result"]: - exit_args.setdefault(name, {})["randompassword"] = \ - result["result"]["randompassword"] + if one_name: + exit_args["randompassword"] = \ + result["result"]["randompassword"] + else: + exit_args.setdefault(name, {})["randompassword"] = \ + result["result"]["randompassword"] + + def exception_handler(module, ex, exit_args, one_name): + if ex.exception == ipalib_errors.EmptyModlist: + result = {} + return False exit_args = {} - changed = module.execute_ipa_commands(commands, handle_result, - exit_args=exit_args) + changed = module.execute_ipa_commands(commands, result_handler, + exception_handler, + exit_args=exit_args, + one_name=len(names)==1) - if len(names) == 1: - ansible_module.exit_json(changed=changed, - user=exit_args[names[0]]) - else: - ansible_module.exit_json(changed=changed, user=exit_args) + ansible_module.exit_json(changed=changed, user=exit_args) """ + # Fail on given handlers_user_args without result or exception + # handler + if result_handler is None and exception_handler is None and \ + len(handlers_user_args) > 0: + self.fail_json(msg="Args without result and exception hander: " + "%s" % repr(handlers_user_args)) + + # Fail on given result_handler and fail_on_member_errors + if result_handler is not None and fail_on_member_errors: + self.fail_json( + msg="result_handler given and fail_on_member_errors set") + # No commands, report no changes if commands is None: return False @@ -759,6 +799,24 @@ else: if self.check_mode: return len(commands) > 0 + # Error list for result_handler and error_handler + _errors = [] + + # Handle fail_on_member_errors, set result_handler to + # member_error_handler + # Add internal _errors for result_hendler if the module is not + # adding it. This also activates the final fail_json if + # errors are found. + if fail_on_member_errors: + result_handler = IPAAnsibleModule.member_error_handler + handlers_user_args["errors"] = _errors + elif result_handler is not None: + if "errors" not in handlers_user_args: + # pylint: disable=deprecated-method + argspec = inspect.getargspec(result_handler) + if "errors" in argspec.args: + handlers_user_args["errors"] = _errors + changed = False for name, command, args in commands: try: @@ -773,13 +831,22 @@ else: else: changed = True - if handle_result is not None: - handle_result(self, result, command, name, args, - **handle_result_user_args) + # If result_handler is not None, call it with user args + # defined in **handlers_user_args + if result_handler is not None: + result_handler(self, result, command, name, args, + **handlers_user_args) except Exception as e: + if exception_handler is not None and \ + exception_handler(self, e, **handlers_user_args): + continue self.fail_json(msg="%s: %s: %s" % (command, name, str(e))) + # Fail on errors from result_handler and exception_handler + if len(_errors) > 0: + self.fail_json(msg=", ".join(_errors)) + return changed class FreeIPABaseModule(IPAAnsibleModule):