diff --git a/changelogs/fragments/callback_fixes.yml b/changelogs/fragments/callback_fixes.yml new file mode 100644 index 00000000000..5acc8e4ff6a --- /dev/null +++ b/changelogs/fragments/callback_fixes.yml @@ -0,0 +1,2 @@ +bugfixes: + - Update callbacks to use Ansible's JSON encoder to avoid known serialization issues diff --git a/lib/ansible/plugins/callback/json.py b/lib/ansible/plugins/callback/json.py index 3961a78aab9..e1f511cc464 100644 --- a/lib/ansible/plugins/callback/json.py +++ b/lib/ansible/plugins/callback/json.py @@ -35,7 +35,7 @@ import json from functools import partial from ansible.inventory.host import Host - +from ansible.parsing.ajson import AnsibleJSONEncoder from ansible.plugins.callback import CallbackBase @@ -111,7 +111,7 @@ class CallbackModule(CallbackBase): 'custom_stats': custom_stats, } - self._display.display(json.dumps(output, indent=4, sort_keys=True)) + self._display.display(json.dumps(output, cls=AnsibleJSONEncoder, indent=4, sort_keys=True)) def _record_task_result(self, on_info, result, **kwargs): """This function is used as a partial to add failed/skipped info in a single method""" diff --git a/lib/ansible/plugins/callback/log_plays.py b/lib/ansible/plugins/callback/log_plays.py index 146165fa3a0..f6986208cd1 100644 --- a/lib/ansible/plugins/callback/log_plays.py +++ b/lib/ansible/plugins/callback/log_plays.py @@ -24,6 +24,7 @@ import json from collections import MutableMapping from ansible.module_utils._text import to_bytes +from ansible.parsing.ajson import AnsibleJSONEncoder from ansible.plugins.callback import CallbackBase @@ -61,7 +62,7 @@ class CallbackModule(CallbackBase): else: data = data.copy() invocation = data.pop('invocation', None) - data = json.dumps(data) + data = json.dumps(data, cls=AnsibleJSONEncoder) if invocation is not None: data = json.dumps(invocation) + " => %s " % data diff --git a/lib/ansible/plugins/callback/mail.py b/lib/ansible/plugins/callback/mail.py index 18f6fca7ff6..98c36ff651f 100644 --- a/lib/ansible/plugins/callback/mail.py +++ b/lib/ansible/plugins/callback/mail.py @@ -70,6 +70,7 @@ import smtplib from ansible.module_utils.six import string_types from ansible.module_utils._text import to_bytes +from ansible.parsing.ajson import AnsibleJSONEncoder from ansible.plugins.callback import CallbackBase @@ -207,7 +208,7 @@ class CallbackModule(CallbackBase): body += self.body_blob(result._result['deprecations'][i], 'exception %d' % (i + 1)) body += 'and a complete dump of the error:\n\n' - body += self.indent('%s: %s' % (failtype, json.dumps(result._result, indent=4))) + body += self.indent('%s: %s' % (failtype, json.dumps(result._result, cls=AnsibleJSONEncoder, indent=4))) self.mail(subject=subject, body=body) @@ -230,4 +231,4 @@ class CallbackModule(CallbackBase): def v2_runner_item_on_failed(self, result): # Pass item information to task failure self.itemsubject = result._result['msg'] - self.itembody += self.body_blob(json.dumps(result._result, indent=4), "failed item dump '%(item)s'" % result._result) + self.itembody += self.body_blob(json.dumps(result._result, cls=AnsibleJSONEncoder, indent=4), "failed item dump '%(item)s'" % result._result) diff --git a/lib/ansible/plugins/callback/splunk.py b/lib/ansible/plugins/callback/splunk.py index 3517e96db52..26b5d1d8853 100644 --- a/lib/ansible/plugins/callback/splunk.py +++ b/lib/ansible/plugins/callback/splunk.py @@ -70,8 +70,9 @@ import getpass from datetime import datetime from os.path import basename -from ansible.plugins.callback import CallbackBase from ansible.module_utils.urls import open_url +from ansible.parsing.ajson import AnsibleJSONEncoder +from ansible.plugins.callback import CallbackBase class SplunkHTTPCollectorSource(object): @@ -116,7 +117,7 @@ class SplunkHTTPCollectorSource(object): data['ansible_result'] = result._result # This wraps the json payload in and outer json event needed by Splunk - jsondata = json.dumps(data, sort_keys=True) + jsondata = json.dumps(data, cls=AnsibleJSONEncoder, sort_keys=True) jsondata = '{"event":' + jsondata + "}" open_url( diff --git a/lib/ansible/plugins/callback/sumologic.py b/lib/ansible/plugins/callback/sumologic.py index 69b457f33fe..4a2bd0b5bb0 100644 --- a/lib/ansible/plugins/callback/sumologic.py +++ b/lib/ansible/plugins/callback/sumologic.py @@ -61,8 +61,9 @@ import getpass from datetime import datetime from os.path import basename -from ansible.plugins.callback import CallbackBase from ansible.module_utils.urls import open_url +from ansible.parsing.ajson import AnsibleJSONEncoder +from ansible.plugins.callback import CallbackBase class SumologicHTTPCollectorSource(object): @@ -108,7 +109,7 @@ class SumologicHTTPCollectorSource(object): open_url( url, - data=json.dumps(data, sort_keys=True), + data=json.dumps(data, cls=AnsibleJSONEncoder, sort_keys=True), headers={ 'Content-type': 'application/json', 'X-Sumo-Host': data['ansible_host'] diff --git a/lib/ansible/plugins/callback/yaml.py b/lib/ansible/plugins/callback/yaml.py index 50d334a5a9e..756fb79bf22 100644 --- a/lib/ansible/plugins/callback/yaml.py +++ b/lib/ansible/plugins/callback/yaml.py @@ -24,11 +24,12 @@ import json import re import string import sys + +from ansible.module_utils._text import to_bytes, to_text +from ansible.module_utils.six import string_types +from ansible.parsing.yaml.dumper import AnsibleDumper from ansible.plugins.callback import CallbackBase, strip_internal_keys from ansible.plugins.callback.default import CallbackModule as Default -from ansible.module_utils.six import string_types -from ansible.module_utils._text import to_bytes, to_text -from ansible.parsing.yaml.dumper import AnsibleDumper # from http://stackoverflow.com/a/15423007/115478 @@ -81,7 +82,7 @@ class CallbackModule(Default): def _dump_results(self, result, indent=None, sort_keys=True, keep_invocation=False): if result.get('_ansible_no_log', False): - return json.dumps(dict(censored="the output has been hidden due to the fact that 'no_log: true' was specified for this result")) + return json.dumps(dict(censored="The output has been hidden due to the fact that 'no_log: true' was specified for this result")) # All result keys stating with _ansible_ are internal, so remove them from the result before we output anything. abridged_result = strip_internal_keys(result)