def _dump_results(self, result, indent=None, sort_keys=True, keep_invocation=False): if not indent and (result.get('_ansible_verbose_always') or self._display.verbosity > 2): indent = 4 # All result keys stating with _ansible_ are internal, so remove them from the result before we output anything. abridged_result = strip_internal_keys(module_response_deepcopy(result)) # remove invocation unless specifically wanting it if not keep_invocation and self._display.verbosity < 3 and 'invocation' in result: del abridged_result['invocation'] # remove diff information from screen output if self._display.verbosity < 3 and 'diff' in result: del abridged_result['diff'] # remove exception from screen output if 'exception' in abridged_result: del abridged_result['exception'] try: jsonified_results = json.dumps(abridged_result, cls=AnsibleJSONEncoder, indent=indent, ensure_ascii=False, sort_keys=sort_keys) except TypeError: # Python3 bug: throws an exception when keys are non-homogenous types: # https://bugs.python.org/issue25457 # sort into an OrderedDict and then json.dumps() that instead if not OrderedDict: raise jsonified_results = json.dumps(OrderedDict(sorted(abridged_result.items(), key=to_text)), cls=AnsibleJSONEncoder, indent=indent, ensure_ascii=False, sort_keys=False) return jsonified_results
def _load_result(self, result, status, **kwargs): """ This method is called when an individual task instance on a single host completes. It is responsible for logging a single result to the database. """ hostname = result._host.get_name() self.result_ended[hostname] = datetime.datetime.now( datetime.timezone.utc).isoformat() # Retrieve the host so we can associate the result to the host id host = self._get_or_create_host(hostname) results = strip_internal_keys(module_response_deepcopy(result._result)) # Round-trip through JSON to sort keys and convert Ansible types # to standard types try: jsonified = json.dumps(results, cls=AnsibleJSONEncoder, ensure_ascii=False, sort_keys=True) except TypeError: # Python 3 can't sort non-homogenous keys. # https://bugs.python.org/issue25457 jsonified = json.dumps(results, cls=AnsibleJSONEncoder, ensure_ascii=False, sort_keys=False) results = json.loads(jsonified) # Sanitize facts if "ansible_facts" in results: for fact in self.ignored_facts: if fact in results["ansible_facts"]: self.log.debug("Ignoring fact: %s" % fact) results["ansible_facts"][ fact] = "Not saved by ARA as configured by 'ignored_facts'" self.result = self.client.post( "/api/v1/results", playbook=self.playbook["id"], task=self.task["id"], host=host["id"], play=self.task["play"], content=results, status=status, started=self.result_started[hostname] if hostname in self.result_started else self.task["started"], ended=self.result_ended[hostname], changed=result._result.get("changed", False), # Note: ignore_errors might be None instead of a boolean ignore_errors=kwargs.get("ignore_errors", False) or False, ) if self.task["action"] in ["setup", "gather_facts" ] and "ansible_facts" in results: self.client.patch("/api/v1/hosts/%s" % host["id"], facts=results["ansible_facts"])
def clean_copy(self): ''' returns 'clean' taskresult object ''' # FIXME: clean task_fields, _task and _host copies result = TaskResult(self._host, self._task, {}, self._task_fields) # statuses are already reflected on the event type if result._task and result._task.action in ['debug']: # debug is verbose by default to display vars, no need to add invocation ignore = _IGNORE + ('invocation', ) else: ignore = _IGNORE subset = {} # preserve subset for later for sub in _SUB_PRESERVE: if sub in self._result: subset[sub] = {} for key in _SUB_PRESERVE[sub]: if key in self._result[sub]: subset[sub][key] = self._result[sub][key] if isinstance(self._task.no_log, bool) and self._task.no_log or self._result.get( '_ansible_no_log', False): x = { "censored": "the output has been hidden due to the fact that 'no_log: true' was specified for this result" } # preserve full for preserve in _PRESERVE: if preserve in self._result: x[preserve] = self._result[preserve] result._result = x elif self._result: result._result = module_response_deepcopy(self._result) # actualy remove for remove_key in ignore: if remove_key in result._result: del result._result[remove_key] # remove almost ALL internal keys, keep ones relevant to callback strip_internal_keys(result._result, exceptions=CLEAN_EXCEPTIONS) # keep subset result._result.update(subset) return result
def _dump_results(self, result, indent=None, sort_keys=True, keep_invocation=False, serialize=True): try: result_format = self.get_option('result_format') except KeyError: # Callback does not declare result_format nor extend result_format_callback result_format = 'json' try: pretty_results = self.get_option('pretty_results') except KeyError: # Callback does not declare pretty_results nor extend result_format_callback pretty_results = None indent_conditions = ( result.get('_ansible_verbose_always'), pretty_results is None and result_format != 'json', pretty_results is True, self._display.verbosity > 2, ) if not indent and any(indent_conditions): indent = 4 if pretty_results is False: # pretty_results=False overrides any specified indentation indent = None # All result keys stating with _ansible_ are internal, so remove them from the result before we output anything. abridged_result = strip_internal_keys(module_response_deepcopy(result)) # remove invocation unless specifically wanting it if not keep_invocation and self._display.verbosity < 3 and 'invocation' in result: del abridged_result['invocation'] # remove diff information from screen output if self._display.verbosity < 3 and 'diff' in result: del abridged_result['diff'] # remove exception from screen output if 'exception' in abridged_result: del abridged_result['exception'] if not serialize: # Just return ``abridged_result`` without going through serialization # to permit callbacks to take advantage of ``_dump_results`` # that want to further modify the result, or use custom serialization return abridged_result if result_format == 'json': try: return json.dumps(abridged_result, cls=AnsibleJSONEncoder, indent=indent, ensure_ascii=False, sort_keys=sort_keys) except TypeError: # Python3 bug: throws an exception when keys are non-homogenous types: # https://bugs.python.org/issue25457 # sort into an OrderedDict and then json.dumps() that instead if not OrderedDict: raise return json.dumps(OrderedDict( sorted(abridged_result.items(), key=to_text)), cls=AnsibleJSONEncoder, indent=indent, ensure_ascii=False, sort_keys=False) elif result_format == 'yaml': # None is a sentinel in this case that indicates default behavior # default behavior for yaml is to prettify results lossy = pretty_results in (None, True) if lossy: # if we already have stdout, we don't need stdout_lines if 'stdout' in abridged_result and 'stdout_lines' in abridged_result: abridged_result['stdout_lines'] = '<omitted>' # if we already have stderr, we don't need stderr_lines if 'stderr' in abridged_result and 'stderr_lines' in abridged_result: abridged_result['stderr_lines'] = '<omitted>' return '\n%s' % textwrap.indent( yaml.dump( abridged_result, allow_unicode=True, Dumper=_AnsibleCallbackDumper(lossy=lossy), default_flow_style=False, indent=indent, # sort_keys=sort_keys # This requires PyYAML>=5.1 ), ' ' * (indent or 4))
def test_module_response_deepcopy_dict(): x = {"foo": [1, 2], "bar": 3} y = module_response_deepcopy(x) assert y == x assert x is not y assert x["foo"] is not y["foo"]
def test_module_response_deepcopy_tuple_of_immutables(): x = ((1, 2), 3) y = module_response_deepcopy(x) assert x is y
def test_module_response_deepcopy_tuple(): x = ([1, 2], 3) y = module_response_deepcopy(x) assert y == x assert x is not y assert x[0] is not y[0]
def test_module_response_deepcopy_empty_tuple(): x = () y = module_response_deepcopy(x) assert x is y
def test_module_response_deepcopy_list(): x = [[1, 2], 3] y = module_response_deepcopy(x) assert y == x assert x is not y assert x[0] is not y[0]
def test_module_response_deepcopy_atomic(): tests = [None, 42, 2**100, 3.14, True, False, 1j, "hello", "hello\u1234"] for x in tests: assert module_response_deepcopy(x) is x
def test_module_response_deepcopy_basic(): x = 42 y = module_response_deepcopy(x) assert y == x
def _load_result(self, result, status, **kwargs): """ This method is called when an individual task instance on a single host completes. It is responsible for logging a single result to the database. """ hostname = result._host.get_name() self.result_ended[hostname] = datetime.datetime.now( datetime.timezone.utc).isoformat() # Retrieve the host so we can associate the result to the host id host = self._get_or_create_host(hostname) # If the task was delegated to another host, retrieve that too. # Since a single task can be delegated to multiple hosts (ex: looping on a host group and using delegate_to) # this must be a list of hosts. delegated_to = [] # The value of result._task.delegate_to doesn't get templated if the task was skipped # https://github.com/ansible/ansible/issues/75339#issuecomment-888724838 if result._task.delegate_to and status != "skipped": task_uuid = str(result._task._uuid[:36]) if task_uuid in self.delegation_cache: for delegated in self.delegation_cache[task_uuid]: delegated_to.append(self._get_or_create_host(delegated)) else: delegated_to.append( self._get_or_create_host(result._task.delegate_to)) # Retrieve the task so we can associate the result to the task id task = self._get_or_create_task(result._task) results = strip_internal_keys(module_response_deepcopy(result._result)) # Round-trip through JSON to sort keys and convert Ansible types # to standard types try: jsonified = json.dumps(results, cls=AnsibleJSONEncoder, ensure_ascii=False, sort_keys=True) except TypeError: # Python 3 can't sort non-homogenous keys. # https://bugs.python.org/issue25457 jsonified = json.dumps(results, cls=AnsibleJSONEncoder, ensure_ascii=False, sort_keys=False) results = json.loads(jsonified) # Sanitize facts if "ansible_facts" in results: for fact in self.ignored_facts: if fact in results["ansible_facts"]: self.log.debug("Ignoring fact: %s" % fact) results["ansible_facts"][ fact] = "Not saved by ARA as configured by 'ignored_facts'" self.result = self.client.post( "/api/v1/results", playbook=self.playbook["id"], task=task["id"], host=host["id"], delegated_to=[h["id"] for h in delegated_to], play=task["play"], content=results, status=status, started=self.result_started[hostname] if hostname in self.result_started else task["started"], ended=self.result_ended[hostname], changed=result._result.get("changed", False), # Note: ignore_errors might be None instead of a boolean ignore_errors=kwargs.get("ignore_errors", False) or False, ) if task["action"] in ["setup", "gather_facts" ] and "ansible_facts" in results: self.client.patch("/api/v1/hosts/%s" % host["id"], facts=results["ansible_facts"])